﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;

namespace CodeScrapWindow
{
	/// <summary>
	/// This is the class that implements the package exposed by this assembly.
	///
	/// The minimum requirement for a class to be considered a valid package for Visual Studio
	/// is to implement the IVsPackage interface and register itself with the shell.
	/// This package uses the helper classes defined inside the Managed Package Framework (MPF)
	/// to do it: it derives from the Package class that provides the implementation of the 
	/// IVsPackage interface and uses the registration attributes defined in the framework to 
	/// register itself and its components with the shell.
	/// </summary>
	// This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
	// a package.
	[PackageRegistration(UseManagedResourcesOnly = true)]
	// This attribute is used to register the information needed to show the this package
	// in the Help/About dialog of Visual Studio.
	[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
	// This attribute is needed to let the shell know that this package exposes some menus.
	[ProvideMenuResource("Menus.ctmenu", 1)]
	// This attribute registers a tool window exposed by this package.
	[ProvideToolWindow(typeof(CodeScrapToolWindow))]
	[Guid(GuidList.guidCodeScrapPkgString)]
	public sealed class CodeScrapPackage : Package
	{
		// Default the selected compiler to C#
		private string _selectedCompiler = CSharpCompilerProvider.CompilerName;

		/// <summary>
		/// Default constructor of the package.
		/// Inside this method you can place any initialization code that does not require 
		/// any Visual Studio service because at this point the package object is created but 
		/// not sited yet inside Visual Studio environment. The place to do all the other 
		/// initialization is the Initialize method.
		/// </summary>
		public CodeScrapPackage()
		{
			Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
		}

		/// <summary>
		/// This function is called when the user clicks the menu item that shows the 
		/// tool window. See the Initialize method to see how the menu item is associated to 
		/// this function using the OleMenuCommandService service and the MenuCommand class.
		/// </summary>
		private void ShowToolWindow(object sender, EventArgs e)
		{
			// Get the instance number 0 of this tool window. This window is single instance so this instance
			// is actually the only one.
			// The last flag is set to true so that if the tool window does not exists it will be created.
			ToolWindowPane window = this.FindToolWindow(typeof(CodeScrapToolWindow), 0, true);
			if (window == null || window.Frame == null)
			{
				throw new NotSupportedException(Resources.CanNotCreateWindow);
			}

			IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
			ErrorHandler.ThrowOnFailure(windowFrame.Show());
		}

		/// <summary>
		/// Initialization of the package; this method is called right after the package is sited, so this is the place
		/// where you can put all the initialization code that rely on services provided by VisualStudio.
		/// </summary>
		protected override void Initialize()
		{
			Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
			base.Initialize();

			// Add our command handlers for menu (commands must exist in the .vsct file)
			OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
			if (mcs != null)
			{
				// Create the command for the execute button
				CommandID executeCommandID = new CommandID(GuidList.guidCodeScrapCmdSet, (int)PkgCmdIDList.cmdidExecuteCmd);
				MenuCommand executeButtonCommand = new MenuCommand(ExecuteButtonClicked, executeCommandID);
				mcs.AddCommand(executeButtonCommand);

				// Create the list command for filling the language combo
				CommandID languageListCommandID = new CommandID(GuidList.guidCodeScrapCmdSet, (int)PkgCmdIDList.cmdidCompilerComboList);
				MenuCommand languageComboListCommand = new OleMenuCommand(CompilerComboGetList, languageListCommandID);
				mcs.AddCommand(languageComboListCommand);

				// Create the command for the language combo
				CommandID languageCommandID = new CommandID(GuidList.guidCodeScrapCmdSet, (int)PkgCmdIDList.cmdidCompilerCombo);
				OleMenuCommand languageComboCommand = new OleMenuCommand(CompilerComboItemSelected, languageCommandID);
				languageComboCommand.ParametersDescription = "$";
				mcs.AddCommand(languageComboCommand);

				// Create the command for the tool window
				CommandID toolwndCommandID = new CommandID(GuidList.guidCodeScrapCmdSet, (int)PkgCmdIDList.cmdidCodeScrap);
				MenuCommand menuToolWin = new MenuCommand(ShowToolWindow, toolwndCommandID);
				mcs.AddCommand(menuToolWin);
			}
		}

		/// <summary>
		/// This function is the callback used to execute a command when the execute button is clicked.
		/// </summary>
		private void ExecuteButtonClicked(object sender, EventArgs e)
		{
			CodeScrapToolWindow toolWindow = GetToolWindow();
			CodeScrapControl control = (CodeScrapControl)toolWindow.Content;
			control.ViewModel.Code = toolWindow.GetCode();
			control.ViewModel.ExecuteCommand.Execute(null);
		}

		/// <summary>
		/// This function is the callback used to get the items to fill the language combo with.
		/// </summary>
		private void CompilerComboGetList(object sender, EventArgs e)
		{
			if (e == null || e == EventArgs.Empty)
			{
				throw new ArgumentNullException(Resources.EventArgsRequired);
			}

			OleMenuCmdEventArgs eventArgs = e as OleMenuCmdEventArgs;
			if (eventArgs != null)
			{
				if (eventArgs.InValue != null)
				{
					throw new ArgumentException(Resources.InValueInvalid);
				}
				else if (eventArgs.OutValue == IntPtr.Zero)
				{
					throw new ArgumentException(Resources.OutValueRequired);
				}
				else
				{
					Marshal.GetNativeVariantForObject(CompilerFactory.SupportedCompilers, eventArgs.OutValue);
				}
			}
		}

		/// <summary>
		/// This function is the callback used to execute a command when an item in the language combo is selected.
		/// </summary>
		private void CompilerComboItemSelected(object send, EventArgs e)
		{
			if (e == null || e == EventArgs.Empty)
			{
				throw new ArgumentException(Resources.EventArgsRequired);
			}

			OleMenuCmdEventArgs eventArgs = (OleMenuCmdEventArgs)e;

			if (eventArgs.OutValue != IntPtr.Zero && eventArgs.InValue != null)
			{
				throw new ArgumentException(Resources.InAndOutValuesInvalid);
			}
			else if (eventArgs.OutValue != IntPtr.Zero)
			{
				// The combo is requesting the selected value
				Marshal.GetNativeVariantForObject(_selectedCompiler, eventArgs.OutValue);
			}
			else if (eventArgs.InValue != null)
			{
				// The user has selected a value
				bool validInput = false;
				int indexInput = -1;
				for (indexInput = 0; indexInput < CompilerFactory.SupportedCompilers.Length; indexInput++)
				{
					if (string.Equals(CompilerFactory.SupportedCompilers[indexInput], (string)eventArgs.InValue, StringComparison.CurrentCultureIgnoreCase))
					{
						validInput = true;
						break;
					}
				}

				if (validInput)
				{
					_selectedCompiler = CompilerFactory.SupportedCompilers[indexInput];

					CodeScrapToolWindow toolWindow = GetToolWindow();
					CodeScrapControl control = (CodeScrapControl)toolWindow.Content;
					control.ViewModel.Compiler = _selectedCompiler;
				}
				else
				{
					throw new ArgumentException(Resources.InValueNotFound);
				}
			}
			else
			{
				throw new ArgumentException(Resources.InAndOutValuesInvalid);
			}
		}

		private CodeScrapToolWindow GetToolWindow()
		{
			ToolWindowPane window = this.FindToolWindow(typeof(CodeScrapToolWindow), 0, false);
			if (window == null || window.Frame == null)
			{
				throw new NotSupportedException(Resources.CanNotCreateWindow);
			}

			return (CodeScrapToolWindow)window;
		}
	}
}
